use std::{fs, str::FromStr};

use crate::Puzzle;

enum Command {
    Forward(u32),
    Down(u32),
    Up(u32),
}

impl FromStr for Command {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split_whitespace().collect();
        if parts.len() != 2 {
            return Err(format!("Invalid command format: {}", s));
        }

        let value = parts[1]
            .parse::<u32>()
            .map_err(|_| format!("Invalid number: {}", parts[1]))?;

        match parts[0] {
            "forward" => Ok(Command::Forward(value)),
            "down" => Ok(Command::Down(value)),
            "up" => Ok(Command::Up(value)),
            _ => Err(format!("Unknown command: {}", parts[0])),
        }
    }
}

pub struct Dive {
    commands: Vec<Command>,
}

impl Dive {
    #[allow(dead_code)]
    fn new_with_commands(commands: Vec<Command>) -> Self {
        Self { commands }
    }
}

impl Puzzle for Dive {
    type Answer = u32;

    #[allow(dead_code)]
    fn new() -> Self {
        let input = fs::read_to_string("input/day02.md").unwrap();

        let commands = input
            .lines()
            .map(|line| line.trim().parse::<Command>())
            .collect::<Result<Vec<Command>, String>>();

        Self {
            commands: commands.unwrap_or_else(|_| Vec::new()),
        }
    }

    fn solve_part1(&self) -> Self::Answer {
        let mut x: u32 = 0;
        let mut y: u32 = 0;

        for command in &self.commands {
            match command {
                Command::Forward(value) => x += value,
                Command::Down(value) => y += value,
                Command::Up(value) => y -= value,
            }
        }

        x * y
    }

    fn solve_part2(&self) -> Self::Answer {
        let mut x: u32 = 0;
        let mut y: u32 = 0;
        let mut aim: u32 = 0;

        for command in &self.commands {
            match command {
                Command::Down(value) => aim += value,
                Command::Up(value) => aim -= value,
                Command::Forward(value) => {
                    x += value;

                    let delta_y = aim * value;
                    y += delta_y;
                }
            }
        }

        x * y
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::Puzzle;

    fn new() -> Dive {
        let commands = vec![
            Command::Forward(5),
            Command::Down(5),
            Command::Forward(8),
            Command::Up(3),
            Command::Down(8),
            Command::Forward(2),
        ];

        Dive::new_with_commands(commands)
    }

    #[test]
    fn part1() {
        let dive = new();
        assert_eq!(dive.solve_part1(), 150);
    }

    #[test]
    fn part2() {
        let dive = new();
        assert_eq!(dive.solve_part2(), 900);
    }
}
